package com.keqiang.chart.impl.pie

import android.animation.ValueAnimator
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import com.keqiang.chart.base.IDataChangeObserver
import com.keqiang.chart.base.ITouchHandler
import com.keqiang.chart.utils.ClickListener
import com.keqiang.chart.utils.to360Range
import kotlin.math.abs
import kotlin.math.acos
import kotlin.math.sqrt

/**
 * 触摸事件处理基类
 *
 * @author Created by wanggaowan on 2022/8/4 15:45
 */
abstract class BaseTouchHandler<C : BasePieChart<*>, D>(val chart: C) : ITouchHandler {

    protected open var captureTouchEvent = false
    protected open var firstMoveAngle: Float? = null
    protected open var animator: ValueAnimator? = null

    /**
     * 旋转角度
     */
    var rotation: Float = 0f
        private set

    /**
     * 被触摸区域对应的数据，主动设置相当于模拟用户点击
     */
    open var touchData: D? = null

    /**
     * 点击监听
     */
    open var clickListener: ClickListener<D>? = null

    protected open var oldTouchData: D? = null

    /**
     * 是否可以触摸旋转
     */
    open var rotationEnable: Boolean = true
        set(value) {
            field = value
            if (!field) {
                if (animator != null && animator!!.isStarted) {
                    animator?.cancel()
                }
            }
        }

    /**
     * 是否可点击
     */
    open var clickEnable: Boolean = true

    init {
        chart.addDataChangeObserver(object : IDataChangeObserver {
            override fun onChange() {
                touchData = null
            }
        })
    }

    override fun onTouch(event: MotionEvent): Boolean {
        if (!clickEnable && !rotationEnable) {
            return false
        }

        val use = detector.onTouchEvent(event)
        if (event.action == MotionEvent.ACTION_UP
            || event.action == MotionEvent.ACTION_CANCEL) {
            chart.view.parent.requestDisallowInterceptTouchEvent(false)
            return false
        }
        return use
    }

    private val detector =
        GestureDetector(chart.view.context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onDown(e: MotionEvent): Boolean {
                return this@BaseTouchHandler.onDown(e)
            }

            override fun onSingleTapUp(event: MotionEvent): Boolean {
                return this@BaseTouchHandler.onSingleTapUp(event)
            }

            override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
                return this@BaseTouchHandler.onScroll(e1, e2, distanceX, distanceY)
            }

            override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
                if (e1 == null) {
                    return false
                }
                return this@BaseTouchHandler.onFling(e1, e2, velocityX, velocityY)
            }
        })

    protected open fun onDown(e: MotionEvent): Boolean {
        if (animator != null && animator!!.isStarted) {
            animator?.cancel()
        }

        firstMoveAngle = null
        captureTouchEvent = false
        oldTouchData = findClickData(e) ?: return false
        chart.view.parent.requestDisallowInterceptTouchEvent(true)
        return true
    }

    protected open fun onSingleTapUp(event: MotionEvent): Boolean {
        if (!clickEnable) {
            return false
        }

        if (touchData != oldTouchData) {
            touchData = oldTouchData
            chart.view.invalidate()
            clickListener?.invoke(touchData)
        } else if (touchData != null) {
            touchData = null
            chart.view.invalidate()
            clickListener?.invoke(oldTouchData)
        } else {
            clickListener?.invoke(null)
        }
        return true
    }

    protected open fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
        if (!rotationEnable) {
            return false
        }

        val angle = getAngleForPoint(e2.x, e2.y)
        if (firstMoveAngle == null) {
            firstMoveAngle = angle
        } else {
            rotation += angle - firstMoveAngle!!
            rotation = rotation.to360Range
            firstMoveAngle = angle
            chart.view.invalidate()
        }
        return true
    }

    protected open fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
        if (!rotationEnable) {
            return false
        }

        flingRotation(e1, velocityX, velocityY)
        return true
    }

    protected open fun flingRotation(event: MotionEvent, velocityX: Float, velocityY: Float) {
        val angleStep: Float
        val (cx, cy) = chart.getCenter()
        val maxAngle = 8f
        val duration: Long
        if (abs(velocityX) > abs(velocityY)) {
            duration = (abs(velocityX) / 5).toLong()
            angleStep = if (velocityX > 0) {
                if (event.y > cy) {
                    -maxAngle
                } else {
                    maxAngle
                }
            } else if (event.y > cy) {
                maxAngle
            } else {
                -maxAngle
            }
        } else {
            duration = (abs(velocityY) / 5).toLong()
            angleStep = if (velocityY > 0) {
                if (event.x > cx) {
                    maxAngle
                } else {
                    -maxAngle
                }
            } else if (event.x > cx) {
                -maxAngle
            } else {
                maxAngle
            }
        }

        val animator = ValueAnimator.ofFloat(1f)
        animator.interpolator = DecelerateInterpolator()
        animator.addUpdateListener {
            rotation += angleStep * (1 - (it.animatedValue as Float))
            rotation = rotation.to360Range
            chart.view.invalidate()
        }
        animator.duration = duration
        animator.start()
        this.animator = animator
    }

    /**
     * 查找被点击区域对应的数据
     */
    abstract fun findClickData(e: MotionEvent): D?

    /**
     * 获取触摸点相对于圆心的旋转角度
     */
    protected fun getAngleForPoint(x: Float, y: Float): Float {
        val (cx, cy) = chart.getCenter()
        val tx: Double = (x - cx).toDouble()
        val ty: Double = (y - cy).toDouble()
        val length = sqrt(tx * tx + ty * ty)
        val r = acos(tx / length)
        var angle = Math.toDegrees(r).toFloat()
        if (y < cy) angle = 360f - angle
        return angle % 360
    }
}

/**
 * 触摸事件处理基类
 *
 * @author Created by wanggaowan on 2022/8/4 15:45
 */
abstract class Base3DTouchHandler<C : BasePieChart3D<*>, D>(chart: C) : BaseTouchHandler<C, D>(chart) {
    /**
     * 是否可以上下翻转
     */
    open var couldFlip: Boolean = true

    /**
     * 翻转角度
     */
    var flipAngle: Float = 0f
        private set

    /**
     * 最大翻转角度
     */
    open var maxFlipAngle = 80f
        set(value) {
            if (value > 90f) {
                field = 90f
            } else if (value < 0f) {
                field = 0f
            }
        }

    /**
     * 最小翻转角度
     */
    open var minFlipAngle = 0f
        set(value) {
            if (value > 90f) {
                field = 90f
            } else if (value < 0f) {
                field = 0f
            }
        }

    protected open var isFlip: Boolean = false
    protected open var isMove: Boolean = false

    override fun onDown(e: MotionEvent): Boolean {
        isFlip = false
        isMove = false
        return super.onDown(e)
    }

    override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
        if (e1 == null) {
            return false
        }

        if (couldFlip && !isMove) {
            if (isFlip || abs(distanceX) < abs(distanceY)) {
                val (cx) = chart.getCenter()
                val width = chart.radius * 0.5f
                if (isFlip || e1.x > cx - width && e1.x < cx + width) {
                    isFlip = true
                    var newFlipAngle = flipAngle + distanceY / 10
                    if (newFlipAngle + chart.flipAngle > maxFlipAngle) {
                        newFlipAngle = maxFlipAngle - chart.flipAngle
                    } else if (newFlipAngle + chart.flipAngle < minFlipAngle) {
                        newFlipAngle = minFlipAngle - chart.flipAngle
                    }

                    if (newFlipAngle != flipAngle) {
                        flipAngle = newFlipAngle
                        chart.view.invalidate()
                    }
                    return true
                }
            }
        }

        isMove = super.onScroll(e1, e2, distanceX, distanceY)
        return isMove
    }

    override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
        if (isFlip) {
            return false
        }

        return super.onFling(e1, e2, velocityX, velocityY)
    }
}
